// Package encryption provides implementations for JSON file encryption and decryption using AES.
package encryption

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"hash"
	"io"

	"golang.org/x/crypto/pbkdf2"
)

// AESEncryption implements JSONEncryption using AES-256-CBC encryption.
type AESEncryption struct{}

// encryptedPackage represents the structure of an encrypted file.
type encryptedPackage struct {
	Salt       string `json:"salt"`
	IV         string `json:"iv"`
	Ciphertext string `json:"ciphertext"`
}

// Encrypt encrypts a JSON file using AES-256-CBC encryption.
func (a *AESEncryption) Encrypt(input io.Reader, output io.Writer, password string) error {
	// Read the input data
	data, err := io.ReadAll(input)
	if err != nil {
		return fmt.Errorf("failed to read input: %w", err)
	}

	// Generate random salt and IV
	salt := make([]byte, 16)
	iv := make([]byte, 16)
	if _, err := io.ReadFull(rand.Reader, salt); err != nil {
		return fmt.Errorf("failed to generate salt: %w", err)
	}
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return fmt.Errorf("failed to generate IV: %w", err)
	}

	// Derive key using PBKDF2
	key := pbkdf2.Key([]byte(password), salt, 100000, 32, sha256.New)

	// Create cipher
	block, err := aes.NewCipher(key)
	if err != nil {
		return fmt.Errorf("failed to create cipher: %w", err)
	}

	// Apply padding
	paddedData := padPKCS7(data, aes.BlockSize)

	// Encrypt data
	ciphertext := make([]byte, len(paddedData))
	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(ciphertext, paddedData)

	// Encode data with base64
	encodedPackage := encryptedPackage{
		Salt:       base64.StdEncoding.EncodeToString(salt),
		IV:         base64.StdEncoding.EncodeToString(iv),
		Ciphertext: base64.StdEncoding.EncodeToString(ciphertext),
	}

	// Write to output
	encoder := json.NewEncoder(output)
	if err := encoder.Encode(encodedPackage); err != nil {
		return fmt.Errorf("failed to write encrypted data: %w", err)
	}

	return nil
}

// Decrypt decrypts an AES-256-CBC encrypted JSON file.
func (a *AESEncryption) Decrypt(input io.Reader, output io.Writer, password string) error {
	// Read and decode the encrypted package
	var encryptedData encryptedPackage
	decoder := json.NewDecoder(input)
	if err := decoder.Decode(&encryptedData); err != nil {
		return fmt.Errorf("failed to decode encrypted package: %w", err)
	}

	// Decode base64 fields
	salt, err := base64.StdEncoding.DecodeString(encryptedData.Salt)
	if err != nil {
		return fmt.Errorf("failed to decode salt: %w", err)
	}

	iv, err := base64.StdEncoding.DecodeString(encryptedData.IV)
	if err != nil {
		return fmt.Errorf("failed to decode IV: %w", err)
	}

	ciphertext, err := base64.StdEncoding.DecodeString(encryptedData.Ciphertext)
	if err != nil {
		return fmt.Errorf("failed to decode ciphertext: %w", err)
	}

	// Derive key using PBKDF2
	key := pbkdf2.Key([]byte(password), salt, 100000, 32, sha256.New)

	// Create cipher
	block, err := aes.NewCipher(key)
	if err != nil {
		return fmt.Errorf("failed to create cipher: %w", err)
	}

	// Decrypt data
	mode := cipher.NewCBCDecrypter(block, iv)
	decrypted := make([]byte, len(ciphertext))
	mode.CryptBlocks(decrypted, ciphertext)

	// Remove padding
	plaintext, err := unpadPKCS7(decrypted)
	if err != nil {
		return fmt.Errorf("failed to unpad data: %w", err)
	}

	// Write decrypted data to output
	if _, err := output.Write(plaintext); err != nil {
		return fmt.Errorf("failed to write decrypted data: %w", err)
	}

	return nil
}

// padPKCS7 applies PKCS#7 padding to the data.
func padPKCS7(data []byte, blockSize int) []byte {
	padding := blockSize - len(data)%blockSize
	padtext := make([]byte, padding)
	for i := range padtext {
		padtext[i] = byte(padding)
	}
	return append(data, padtext...)
}

// unpadPKCS7 removes PKCS#7 padding from the data.
func unpadPKCS7(data []byte) ([]byte, error) {
	length := len(data)
	if length == 0 {
		return nil, fmt.Errorf("invalid padding size")
	}
	
	padding := int(data[length-1])
	if padding > length || padding > aes.BlockSize {
		return nil, fmt.Errorf("invalid padding size")
	}
	
	// Check if padding is valid
	for i := 0; i < padding; i++ {
		if data[length-padding+i] != byte(padding) {
			return nil, fmt.Errorf("invalid padding")
		}
	}
	
	return data[:length-padding], nil
}

// sha256Func returns a function that creates a new SHA256 hash
func sha256Func() hash.Hash {
	return sha256.New()
}